#pragma once

#include "connection.h"
#include "sockutil.h"
#include "connector.h"

#include "ioloop.h"
#include "timingwheel.h"

#define defConnTimeout  5
#define defReadTimeout  30

namespace tnet
{
    template<typename Derived>
    Connector<Derived>::Connector()
        : m_idleWheel(nullptr)
        , m_maxConnectTimeout(defConnTimeout)
        , m_maxReadTimeout(defReadTimeout)
        , m_loop(nullptr)
    {
        
    }

    template<typename Derived>
    Connector<Derived>::~Connector()
    {
    }

    template<typename Derived>
    int Connector<Derived>::connect(IOLoop* loop, const Address& addr, const ConnectCallback_t& callback, const std::string& device)
    {
        int fd = SockUtil::create();
        if(fd < 0)
        {
            return fd;    
        } 
        
        if(!device.empty())
        {
            SockUtil::bindDevice(fd, device);
        }

        ConnectionPtr_t conn = std::make_shared<Connection>(loop, fd);

        if(m_idleWheel)
        {
            int afterCheck = m_maxConnectTimeout / 2 + random() % m_maxConnectTimeout;
            m_idleWheel->add(std::bind(&Connector<Derived>::onTimer, this, _1, WeakConnectionPtr_t(conn)), afterCheck * 1000);
        }           
        m_loop = loop;

        m_conn = conn;
        conn->setEventCallback(std::bind(&Connector<Derived>::onConnConnectEvent, this->shared_from_this(), _1, _2, _3, callback));
        return conn->connect(addr);    
    }

    template<typename Derived>
    void Connector<Derived>::onConnConnectEvent(const ConnectionPtr_t& conn, ConnEvent event, const void* context, const ConnectCallback_t& callback)
    {
        switch(event)
        {
            case Conn_ConnectingEvent:
                return;    
            case Conn_ConnectEvent:
                {
                    ConnectCallback_t cb = std::move(callback);

                    conn->setEventCallback(std::bind(&Connector<Derived>::onConnEvent, this->shared_from_this(), _1, _2, _3));
                
                    cb(this->shared_from_this(), true);

                    if(m_idleWheel)
                    {
                        int afterCheck = m_maxReadTimeout / 2 + random() % m_maxReadTimeout;
                        m_idleWheel->add(std::bind(&Connector<Derived>::onTimer, this, _1, WeakConnectionPtr_t(conn)), afterCheck * 1000);
                    }
                }
                break;
            default:
                //here we think error
                callback(this->shared_from_this(), false);
                return;
                break;
        }    
    }

    template<typename Derived>
    void Connector<Derived>::onConnEvent(const ConnectionPtr_t& conn, ConnEvent event, const void* context)
    {
        DerivedPtr_t t = this->shared_from_this();
        switch(event)
        {
            case Conn_ReadEvent:
                {
                    const StackBuffer* buf = (const StackBuffer*)(context);
                    t->handleRead(buf->buffer, buf->count);
                }
                break; 
            case Conn_WriteCompleteEvent:
                t->handleWriteComplete(context);
                break;  
            case Conn_ErrorEvent:
                t->handleError(context);
                break;
            case Conn_CloseEvent:
                t->handleClose(context);
                break;
            default:
                break; 
        }    
    }

    template<typename Derived>
    void Connector<Derived>::send(const std::string& data)
    {
        ConnectionPtr_t conn = m_conn.lock();
        
        if(conn)
        {
            conn->send(data);    
        }    
    }

    template<typename Derived>
    void Connector<Derived>::shutDown()
    {    
        ConnectionPtr_t conn = m_conn.lock();
        
        if(conn)
        {
            conn->shutDown();    
        }
    }

    template<typename Derived>
    void Connector<Derived>::setConnectionTimeout(int16_t timeout)
    { 
        if( timeout > 0 )
        {
            m_maxConnectTimeout = timeout; 
        }
    }

    template<typename Derived>
    void Connector<Derived>::setReadTimeout(int16_t timeout)
    { 
        if( timeout > 0)
        {
            m_maxReadTimeout = timeout; 
           
        }
    }

    template<typename Derived>
    void Connector<Derived>::startTimer()
    {
        if(m_idleWheel == nullptr)
        {
            m_idleWheel = std::make_shared<TimingWheel>(1000, 3600);  
        }
    }

    template<typename Derived>
    void Connector<Derived>::onTimer(const TimingWheelPtr_t& wheel, const WeakConnectionPtr_t& conn)
    {
        ConnectionPtr_t c = conn.lock();
        if(!c)
        {
            return;
        }

        struct timespec t;
        clock_gettime(CLOCK_MONOTONIC, &t);
        uint64_t now = t.tv_sec;
        if(c->isConnecting() || c->isConnected() )
        {
            if(now - c->lastActiveTime() >  ( c->isConnecting() ? m_maxConnectTimeout : (uint64_t)m_maxReadTimeout) )
            {
                if(c->isConnecting())
                {
                    handleConnectTimeout(nullptr);
                }
                else
                {
                    handleReadTimeout(nullptr);
                } 
                return;
            }
            if(c->isConnected())
            {
                 m_idleWheel->add(std::bind(&Connector<Derived>::onTimer, this, _1, WeakConnectionPtr_t(c)), m_maxReadTimeout * 900);
            }
        }	
    };     
}
